package x.ovo.wechat.bot.impl.command;

import lombok.SneakyThrows;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.reflect.TypeReference;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.json.JSON;
import org.dromara.hutool.json.JSONUtil;
import picocli.CommandLine;
import x.ovo.wechat.bot.core.Constant;
import x.ovo.wechat.bot.core.command.CommandExcutor;
import x.ovo.wechat.bot.core.command.CommandManager;
import x.ovo.wechat.bot.core.exception.CommandException;
import x.ovo.wechat.bot.core.exception.PluginException;
import x.ovo.wechat.bot.core.message.TextMessage;
import x.ovo.wechat.bot.core.plugin.Plugin;
import x.ovo.wechat.bot.core.util.CommandUtil;
import x.ovo.wechat.bot.impl.config.ClientConfig;

import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 默认命令管理器
 *
 * @author ovo on 2024/07/12.
 */
public enum DefaultCommandManager implements CommandManager {
    INSTANCE;

    private final Map<String, CommandExcutor> map = new HashMap<>();
    private final Map<String, Set<String>> permissions = new HashMap<>();

    @Override
    @SneakyThrows
    public void register(Plugin plugin) {
        CommandExcutor excutor = plugin.getCommandExcutor();
        if (Objects.isNull(excutor)) return;
        // 构建CommandLine，获取主命令和别名注册到map
        CommandLine line = new CommandLine(excutor);
        this.map.put(StrUtil.addPrefixIfNot(line.getCommandName(), "/"), excutor);
        if (Objects.nonNull(line.getCommandSpec().aliases())) {
            Arrays.stream(line.getCommandSpec().aliases()).forEach(e -> this.map.put(StrUtil.addPrefixIfNot(e, "/"), excutor));
        }
        log.debug("注册插件 [{}] 的命令执行器成功，命令：{} {}", plugin.getPluginDesc().getName(), line.getCommandName(), line.getCommandSpec().aliases());
    }

    @Override
    public void unregister(String command) {
        if (StrUtil.isBlank(command)) return;
        Assert.isTrue(this.map.containsKey(StrUtil.addPrefixIfNot(command, "/")), () -> new PluginException("注销命令失败，命令不存在"));
        // 获取executor并构建CommandLine，移除其主命令和别名对应的映射
        CommandExcutor excutor = this.map.get(StrUtil.addPrefixIfNot(command, "/"));
        CommandLine line = new CommandLine(excutor);
        this.map.remove(StrUtil.addPrefixIfNot(line.getCommandName(), "/"));
        Arrays.stream(line.getCommandSpec().aliases()).forEach(e -> this.map.remove(StrUtil.addPrefixIfNot(e, "/")));
        log.debug("注销插件 [{}] 的命令执行器成功，命令：{}", excutor.getPlugin().getPluginDesc().getName(), line.getCommandName());
    }

    @SneakyThrows
    @Override
    public void execute(TextMessage message) {
        // 获取消息来源的用户名，如果是群聊消息，则消息来源是发言的群成员，先获取备注，若备注为空再获取昵称
        String nickname = message.isGroup()
                ? message.getMember().getNickName()
                : StrUtil.defaultIfBlank(message.getFrom().getRemarkName(), message.getFrom().getNickName());
        String key = CommandUtil.getCommand(message.getContent());
        if (this.map.containsKey(key)) {
            CommandExcutor excutor = this.map.get(key);
            excutor.setMessage(message);
            // 验证是否具备权限执行命令，即是否插件作者，命令是否允许该用户执行
            if (!excutor.getPlugin().getPluginDesc().getAuthors().contains(nickname) && !excutor.hasPermission(nickname)) {
                message.getFrom().sendMessage(excutor.noPermissionTip());
                return;
            }

            // 构建CommandLine，并重定向其输出流、错误流
            CommandLine commandLine = new CommandLine(excutor);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos));
            commandLine.setOut(writer);
            commandLine.setErr(writer);
            commandLine.setExecutionExceptionHandler((ex, commandLine1, fullParseResult) -> {
                writer.print("命令执行出现异常：" + ex.getMessage());
                return 0;
            });

            // 执行命令并获取命令执行的结果
            commandLine.execute(CommandUtil.getArgs(message.getContent()));
            String result = commandLine.getParseResult().asCommandLineList().stream()
                    .map(CommandLine::getExecutionResult)
                    .filter(Objects::nonNull)
                    .map(o -> (String) o)
                    .collect(Collectors.joining("\n"));

            writer.flush();
            String res = StrUtil.defaultIfBlank(result, baos.toString());
            message.getFrom().sendMessage(StrUtil.isBlank(res) ? "命令执行成功，但是没有返回值" : res);
        }
    }

    @Override
    public Set<CommandExcutor> list() {
        return new HashSet<>(this.map.values());
    }

    @Override
    public boolean hasPermission(String command, String user) {
        // 用户是否是bot拥有者，或者用户是否具有该命令的执行权限
        return ClientConfig.get().getOwner().equals(user) || (Objects.nonNull(this.permissions.get(StrUtil.addPrefixIfNot(command, "/"))) && this.permissions.get(StrUtil.addPrefixIfNot(command, "/")).contains(user));
    }

    @Override
    public void addPermission(String command, String user) {
        command = StrUtil.addPrefixIfNot(command, "/");
        if (!this.map.containsKey(command)) throw new CommandException(StrUtil.format("命令 [{}] 不存在", command));
        this.permissions.computeIfAbsent(command, k -> new HashSet<>()).add(user);
    }

    @Override
    public void removePermission(String command, String user) {
        command = StrUtil.addPrefixIfNot(command, "/");
        if (!this.map.containsKey(command)) throw new CommandException(StrUtil.format("命令 [{}] 不存在", command));
        this.permissions.computeIfAbsent(command, k -> new HashSet<>()).remove(user);
    }

    @Override
    public Map<String, Set<String>> getPermissions() {
        return this.permissions;
    }

    @Override
    public Set<String> getPermission(String command) {
        command = StrUtil.addPrefixIfNot(command, "/");
        if (!this.map.containsKey(command)) throw new CommandException(StrUtil.format("命令 [{}] 不存在", command));
        return this.permissions.get(command);
    }

    @Override
    public void savePermissions() {
        FileUtil.writeBytes(JSONUtil.toJsonPrettyStr(this.permissions).getBytes(), Constant.Files.COMMAND_PERMISSION_FILE);
        log.info("命令权限配置写入完成");
    }

    @Override
    public void loadPermissions() {
        if (Constant.Files.COMMAND_PERMISSION_FILE.exists()) {
            JSON json = JSONUtil.readJSON(Constant.Files.COMMAND_PERMISSION_FILE, StandardCharsets.UTF_8);
            this.permissions.putAll(JSONUtil.toBean(json, new TypeReference<>() {
            }));
            log.info("命令权限配置载入完成");
        }
    }

}
